{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "github地址:[https://github.com/cheesezh/python_design_patterns](https://github.com/cheesezh/python_design_patterns)\n",
    "\n",
    "## 背景\n",
    "\n",
    "有6个客户想做产品展示网站，其中3个想做成天猫商城那样的“电商风格”展示页面，其中3个想做成博客园那样的“博客风格”展示博客。应该如何实现？"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "网站风格: 电商风格\n",
      "网站风格: 电商风格\n",
      "网站风格: 电商风格\n",
      "网站风格: 博客风格\n",
      "网站风格: 博客风格\n",
      "网站风格: 博客风格\n"
     ]
    }
   ],
   "source": [
    "class WebSite():\n",
    "    \n",
    "    def __init__(self, name):\n",
    "        self.name = name\n",
    "        \n",
    "    def use(self):\n",
    "        print(\"网站风格:\", self.name)\n",
    "        \n",
    "def main():\n",
    "    web1 = WebSite(\"电商风格\")\n",
    "    web1.use()\n",
    "    \n",
    "    web2 = WebSite(\"电商风格\")\n",
    "    web2.use()\n",
    "    \n",
    "    web3 = WebSite(\"电商风格\")\n",
    "    web3.use()\n",
    "    \n",
    "    web4 = WebSite(\"博客风格\")\n",
    "    web4.use()\n",
    "    \n",
    "    web5 = WebSite(\"博客风格\")\n",
    "    web5.use()\n",
    "    \n",
    "    web6 = WebSite(\"博客风格\")\n",
    "    web6.use()\n",
    "    \n",
    "main()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 点评\n",
    "\n",
    "根据上边的代码，如果要做三个“电商风格”，三个“博客风格”的网站，需要六个网站类的实例，而实际上它们本质上都是一样的代码，如果网站增多，实例数量也会增多，这对服务器的资源浪费很严重。\n",
    "\n",
    "现在各个大型博客网站，电子商务网站，每一个博客或者商家都是一个小型网站，它们根据用户ID号的不同，来区分不同的用户，具体数据和模版可以不同，但是代码核心和数据库却是共享的。\n",
    "\n",
    "这就需要用到享元模式。\n",
    "\n",
    "## 享元模式\n",
    "享元模式，运用共享技术有效的支持大量细粒度的对象。主要包括以下几个类："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "具体Flyweight: 21\n",
      "具体Flyweight: 20\n",
      "具体Flyweight: 19\n",
      "不共享的具体Flyweight: 18\n"
     ]
    }
   ],
   "source": [
    "from abc import ABCMeta, abstractmethod\n",
    "\n",
    "\n",
    "class Flyweight():\n",
    "    \"\"\"\n",
    "    Flyweight类，它是所有具体享元类的超类或接口，通过这个接口，Flyweight可以接受并作用于外部状态。\n",
    "    \"\"\"\n",
    "    __metaclass__ = ABCMeta\n",
    "    \n",
    "    @abstractmethod\n",
    "    def operation(self, extrinsicstate):\n",
    "        pass\n",
    "    \n",
    "    \n",
    "class ConcreteFlyweight(Flyweight):\n",
    "    \"\"\"\n",
    "    ConcreteFlyweight是继承Flyweight超类或者是想Flyweight接口，并为内部状态增加存储空间\n",
    "    \"\"\"\n",
    "    def operation(self, extrinsicstate):\n",
    "        print(\"具体Flyweight:\", extrinsicstate)\n",
    "        \n",
    "        \n",
    "class UnsharedConcreteFlyweight(Flyweight):\n",
    "    \"\"\"\n",
    "    UnsharedConcreteFlyweight是指那些不需要共享的Flyweight子类。因为Flyweight接口，共享成为可能，但它并不强制共享\n",
    "    \"\"\"\n",
    "    def operation(self, extrinsicstate):\n",
    "        print(\"不共享的具体Flyweight:\", extrinsicstate)\n",
    "        \n",
    "        \n",
    "class FlyweightFactory():\n",
    "    \"\"\"\n",
    "    FlyweightFactory是一个享元工厂，用来创建并管理Flyweight对象。它主要是用来确保合理地共享Flyweight，当用户请求一个\n",
    "    Flyweight时，FlyweightFactory对象提供一个已创建的实例或者创建一个（如果不存在的话）。\n",
    "    \"\"\"\n",
    "    def __init__(self):\n",
    "        self.flyweights = dict()\n",
    "        self.flyweights['X'] = ConcreteFlyweight()\n",
    "        self.flyweights['Y'] = ConcreteFlyweight()\n",
    "        self.flyweights['Z'] = ConcreteFlyweight()\n",
    "        \n",
    "    def get_flyweight(self, key):\n",
    "        return self.flyweights[key]\n",
    "    \n",
    "\n",
    "def main():\n",
    "    # 代码外部状态\n",
    "    extrinsicstate = 22\n",
    "    \n",
    "    f = FlyweightFactory()\n",
    "    \n",
    "    fx = f.get_flyweight(\"X\")\n",
    "    extrinsicstate -= 1\n",
    "    fx.operation(extrinsicstate)\n",
    "    \n",
    "    fy = f.get_flyweight(\"Y\")\n",
    "    extrinsicstate -= 1\n",
    "    fy.operation(extrinsicstate)\n",
    "    \n",
    "    fz = f.get_flyweight(\"Z\")\n",
    "    extrinsicstate -= 1\n",
    "    fy.operation(extrinsicstate)\n",
    "    \n",
    "    uf = UnsharedConcreteFlyweight()\n",
    "    extrinsicstate -= 1\n",
    "    uf.operation(extrinsicstate)\n",
    "    \n",
    "main()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 点评\n",
    "\n",
    "上述代码中，FlyweightFactory根据客户需求返回早已生成好的对象，但是实际上不一定需要，完全可以初始化时什么也不做，到需要时，再判断对象是否为null来决定是否实例化；\n",
    "\n",
    "为什么会有UnsharedConcreteFlyweight存在呢？这是因为尽管我们大部分时间都需要共享对象来降低内存损耗，但个别时候也有可能不需要共享的，那么此时的UnsharedConcreteFlyweight就有存在的必要了，它可以解决那些不需要共享对象的问题。\n",
    "\n",
    "## 网站共享代码"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "网站风格: 电商风格\n",
      "网站风格: 电商风格\n",
      "网站风格: 电商风格\n",
      "网站风格: 博客风格\n",
      "网站风格: 博客风格\n",
      "网站风格: 博客风格\n",
      "网站风格总数: 2\n"
     ]
    }
   ],
   "source": [
    "from abc import ABCMeta, abstractmethod\n",
    "\n",
    "\n",
    "class WebSite():\n",
    "    \"\"\"\n",
    "    网站抽象类：Flyweight类，它是所有具体享元类的超类或接口，通过这个接口，Flyweight可以接受并作用于外部状态。\n",
    "    \"\"\"\n",
    "    __metaclass__ = ABCMeta\n",
    "    \n",
    "    @abstractmethod\n",
    "    def use(self):\n",
    "        pass\n",
    "    \n",
    "    \n",
    "class ConcreteWebSite(WebSite):\n",
    "    \"\"\"\n",
    "    具体网站类：ConcreteFlyweight是继承Flyweight超类或者是想Flyweight接口，并为内部状态增加存储空间\n",
    "    \"\"\"\n",
    "    def __init__(self, name):\n",
    "        self.name = name\n",
    "        \n",
    "    def use(self):\n",
    "        print(\"网站风格:\", self.name)\n",
    "        \n",
    "                \n",
    "class WebSiteFactory():\n",
    "    \"\"\"\n",
    "    网站工厂类：FlyweightFactory是一个享元工厂，用来创建并管理Flyweight对象。它主要是用来确保合理地共享Flyweight，\n",
    "    当用户请求一个Flyweight时，FlyweightFactory对象提供一个已创建的实例或者创建一个（如果不存在的话）。\n",
    "    \"\"\"\n",
    "    def __init__(self):\n",
    "        self.flyweights = dict()\n",
    "        \n",
    "    def get_website(self, key):\n",
    "        if key not in self.flyweights:\n",
    "            self.flyweights[key] = ConcreteWebSite(key)\n",
    "        return self.flyweights[key]\n",
    "    \n",
    "\n",
    "def main():\n",
    "    f = WebSiteFactory()\n",
    "    \n",
    "    fx = f.get_website(\"电商风格\")\n",
    "    fx.use()\n",
    "    \n",
    "    fy = f.get_website(\"电商风格\")\n",
    "    fy.use()\n",
    "    \n",
    "    fz = f.get_website(\"电商风格\")\n",
    "    fz.use()\n",
    "    \n",
    "    fa = f.get_website(\"博客风格\")\n",
    "    fa.use()\n",
    "    \n",
    "    fb = f.get_website(\"博客风格\")\n",
    "    fb.use()\n",
    "    \n",
    "    fc = f.get_website(\"博客风格\")\n",
    "    fc.use()\n",
    "    print(\"网站风格总数:\", len(f.flyweights))\n",
    "    \n",
    "main()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 点评\n",
    "\n",
    "这样写基本实现了享元模式共享对象的目的，也就是，无论创建多少个网站，只要是“电商风格”，那就都一样，只要是“博客风格”，也都一样。但是给不同企业创建网站，它们的数据肯定会不同，所以上述代码没有体香对象间的不同，只体现了共享的部分。\n",
    "\n",
    "## 内部状态和外部状态\n",
    "\n",
    "在享元对象内部并且不会随环境改变而改变的共享部分，可以成为是享元对象的内部状态；\n",
    "\n",
    "随着环境改变而改变，不可共享的状态就是享元对象的外部状态；\n",
    "\n",
    "享元模式可以避免大量非常相似类的开销。在程序设计中，有时需要生成大量细粒度的类实例来表示数据。如果能发现这些实例除了几个参数外基本上都是相同的，有时候就能够大幅度地减少需要实例化的类的数量。如果能把那些参数转移到类实例的外面，在方法调用时将它们传递进来，就可以通过共享大幅度地减少单个实例的数目。\n",
    "\n",
    "享元模式Flyweight执行时所需的状态是有内部的，也可能有外部的，内部状态存储于ConcreteFlyweight对象之中，而外部状态则应该考虑有客户端对象存储或计算，当调用Flyweight对象的操作时，将该状态传递给它。\n",
    "\n",
    "在网站的例子中，客户账号就是外部状态，应该由专门的对象来处理。\n",
    "\n",
    "## 带有外部状态的版本"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "贺贺 - 网站风格: 电商风格\n",
      "曼曼 - 网站风格: 电商风格\n",
      "云云 - 网站风格: 电商风格\n",
      "灵灵 - 网站风格: 博客风格\n",
      "依依 - 网站风格: 博客风格\n",
      "灵依 - 网站风格: 博客风格\n",
      "网站风格总数: 2\n"
     ]
    }
   ],
   "source": [
    "from abc import ABCMeta, abstractmethod\n",
    "\n",
    "class User():\n",
    "    \"\"\"\n",
    "    用户类，网站的客户账号，是“网站类”的外部状态\n",
    "    \"\"\"\n",
    "    def __init__(self, name):\n",
    "        self.name = name\n",
    "\n",
    "class WebSite():\n",
    "    \"\"\"\n",
    "    网站抽象类：Flyweight类，它是所有具体享元类的超类或接口，通过这个接口，Flyweight可以接受并作用于外部状态。\n",
    "    \"\"\"\n",
    "    __metaclass__ = ABCMeta\n",
    "    \n",
    "    @abstractmethod\n",
    "    def use(self, user):\n",
    "        pass\n",
    "    \n",
    "    \n",
    "class ConcreteWebSite(WebSite):\n",
    "    \"\"\"\n",
    "    具体网站类：ConcreteFlyweight是继承Flyweight超类或者是想Flyweight接口，并为内部状态增加存储空间\n",
    "    \"\"\"\n",
    "    def __init__(self, name):\n",
    "        self.name = name\n",
    "        \n",
    "    def use(self, user):\n",
    "        print(user.name, \"- 网站风格:\", self.name)\n",
    "        \n",
    "                \n",
    "class WebSiteFactory():\n",
    "    \"\"\"\n",
    "    网站工厂类：FlyweightFactory是一个享元工厂，用来创建并管理Flyweight对象。它主要是用来确保合理地共享Flyweight，\n",
    "    当用户请求一个Flyweight时，FlyweightFactory对象提供一个已创建的实例或者创建一个（如果不存在的话）。\n",
    "    \"\"\"\n",
    "    def __init__(self):\n",
    "        self.flyweights = dict()\n",
    "        \n",
    "    def get_website(self, key):\n",
    "        if key not in self.flyweights:\n",
    "            self.flyweights[key] = ConcreteWebSite(key)\n",
    "        return self.flyweights[key]\n",
    "    \n",
    "\n",
    "def main():\n",
    "    f = WebSiteFactory()\n",
    "    \n",
    "    fx = f.get_website(\"电商风格\")\n",
    "    fx.use(User(\"贺贺\"))\n",
    "    \n",
    "    fy = f.get_website(\"电商风格\")\n",
    "    fy.use(User(\"曼曼\"))\n",
    "    \n",
    "    fz = f.get_website(\"电商风格\")\n",
    "    fz.use(User(\"云云\"))\n",
    "    \n",
    "    fa = f.get_website(\"博客风格\")\n",
    "    fa.use(User(\"灵灵\"))\n",
    "    \n",
    "    fb = f.get_website(\"博客风格\")\n",
    "    fb.use(User(\"依依\"))\n",
    "    \n",
    "    fc = f.get_website(\"博客风格\")\n",
    "    fc.use(User(\"灵依\"))\n",
    "    print(\"网站风格总数:\", len(f.flyweights))\n",
    "    \n",
    "main()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 总结\n",
    "\n",
    "什么时候需要考虑使用享元模式呢？\n",
    "- 如果一个应用程序使用了大量的对象，而大量的这些对象造成了很大的存储开销时就应该考虑使用\n",
    "- 对象的大多数状态都是内部状态，如果可以删除对象的外部状态，那么可以用相对较少的共享对象取代很多组对象\n",
    "\n",
    "在实际使用中，享元模式到底能达到什么效果呢？\n",
    "\n",
    "因为使用了享元模式，所以有了共享对象，实例总数就大大减少了，如果共享的对象越多，存储节约也就越多，节约量随着共享状态的增多而增大。\n",
    "\n",
    "需要注意的是，享元模式需要维护一个记录了系统已有的所有享元的列表，而这本身需要耗费资源，另外享元模式会使系统变得更加复杂。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
